#include <chrono>
#include <cstdio>
#include <iostream>
#include <openssl/bio.h>
#include <openssl/ecdsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

using namespace std;

const int roundNum = 10000;

int SmsSignature(const char* privatekey_path, const char* message, int dlen,
    unsigned char* sig_buf, unsigned int* sig_len)
{

    unsigned char digest[EVP_MAX_MD_SIZE];
    unsigned int digest_len = 0;
    EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();

    EC_KEY* ec_key = NULL;
    BIO* pbio_key_file = NULL;

    /*get key from pem file*/
    pbio_key_file = BIO_new_file(privatekey_path, "rb");
    ec_key = PEM_read_bio_ECPrivateKey(pbio_key_file, NULL, NULL, NULL);
    if (!ec_key) {
        printf("get key fail \n");
        return 0;
    }

    EVP_MD_CTX_init(md_ctx);
    if (!EVP_DigestInit(md_ctx, EVP_sha256())) {
        printf("EVP_digest fail \n");
        return 0;
    }
    if (!EVP_DigestUpdate(md_ctx, (const void*)message, dlen)) {
        printf("EVP_DigestUpdate fail \n");
        return 0;
    }
    if (!EVP_DigestFinal(md_ctx, digest, &digest_len)) {
        printf("EVP_DigestFinal fail \n");
        return 0;
    }
    /*do sign*/
    auto pos1 = chrono::steady_clock::now();
    for (int i = 0; i < roundNum; i++) {
        if (ECDSA_sign(0, digest, digest_len, sig_buf, sig_len, ec_key) != 1) {
            printf("ECDSA_sign fail \n");
            return 0;
        }
    }

    auto pos2 = chrono::steady_clock::now();
    chrono::duration<double, milli> dur1 = pos2 - pos1;
    cout << "Sign [" << roundNum << "]'s signature cost: " << dur1.count() << "ms" << endl;

    if (pbio_key_file) {
        BIO_free(pbio_key_file);
        pbio_key_file = NULL;
    }

    if (ec_key) {
        EC_KEY_free(ec_key);
        ec_key = NULL;
    }
    return 1;
}

int VerifySignature(const char* publickey_path, const char* message, int dlen,
    unsigned char* sig_buf, int sig_len)
{
    BIO* pbio_key_file;
    int ret;
    unsigned char digest[EVP_MAX_MD_SIZE];
    unsigned int digest_len = 0;
    EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();

    unsigned char Cert[4099];
    unsigned long Certlen;
    unsigned char* pTmp = NULL;
    X509* usrCert = NULL; // the struct of X509 certificate
    FILE* fp;
    EC_KEY* ec_key;

    // fp = fopen(publickey_path, "rb");
    // if (fp == NULL) {
    //     printf("read cert file fail \n");
    //     return 0;
    // }
    // Certlen = fread(Cert, 1, 4096, fp);
    // fclose(fp);

    // pTmp = Cert;
    // Convert to x509 data
    // usrCert = d2i_X509(NULL, (const unsigned char**)&pTmp, Certlen);
    // if (usrCert == NULL) {
    /*Determine whether or not is a PEM certificate*/
    //     pbio_key_file = BIO_new_file(publickey_path, "r");
    //     usrCert = PEM_read_bio_X509(pbio_key_file, NULL, NULL, NULL);
    //     if (usrCert == NULL) {
    //         printf("format conver error\n");
    //         return 0;
    //     }
    // }

    // EVP_PKEY* pubKey = X509_get_pubkey(usrCert);
    // if (pubKey->type == EVP_PKEY_EC) {
    // printf("pubkey is ec\n");
    // ec_key = EVP_PKEY_get1_EC_KEY(pubKey);
    // if (!ec_key) {
    //     printf("get key fail \n");
    //     return 0;
    // }
    // }

    /*
    //printf the key data
    printf("EC_key is: \n");
    derpubkeyLen = i2d_EC_PUBKEY(ec_key, &pTmp);
    for (int i = 0; i < derpubkeyLen; i++)
    {
        printf("%02x", derpubkey[i]);
    }
    */

    pbio_key_file = BIO_new(BIO_s_file());
    BIO_read_filename(pbio_key_file, publickey_path);
    ec_key = PEM_read_bio_EC_PUBKEY(pbio_key_file, NULL, NULL, NULL);
    ret = EC_KEY_check_key(ec_key);
    if (ret != 1) {
        printf("EC_KEY_check_key fail \n");
        return 0;
    }

    EVP_MD_CTX_init(md_ctx);
    if (!EVP_DigestInit(md_ctx, EVP_sha256())) {
        printf("EVP_digest fail \n");
        return 0;
    }
    if (!EVP_DigestUpdate(md_ctx, (const void*)message, dlen)) {
        printf("EVP_DigestUpdate fail \n");
        return 0;
    }
    if (!EVP_DigestFinal(md_ctx, digest, &digest_len)) {
        printf("EVP_DigestFinal fail \n");
        return 0;
    }
    /*do verify*/
    auto pos1 = chrono::steady_clock::now();
    for (int i = 0; i < roundNum; i++) {
        ret = ECDSA_verify(0, (const unsigned char*)digest, digest_len, (const unsigned char*)sig_buf, sig_len, ec_key);
        if (ret != 1) {
            printf("ECDSA_verify fail \n");
            return 0;
        }
    }

    auto pos2 = chrono::steady_clock::now();
    chrono::duration<double, milli> dur1 = pos2 - pos1;
    cout << "Verify [" << roundNum << "]'s signature cost: " << dur1.count() << "ms" << endl;

    if (pbio_key_file) {
        BIO_free(pbio_key_file);
        pbio_key_file = NULL;
    }

    if (ec_key) {
        EC_KEY_free(ec_key);
        ec_key = NULL;
    }
    return ret;
}

const string priKeyPath = "./ec_prikey.pem";
const string pubKeyPath = "./ec_pubkey.pem";
unsigned int ecdsa_width = 256;

int main()
{
    string message = "czyczy";
    string signatureBuf;
    signatureBuf.resize(ecdsa_width + 16);
    // sign
    unsigned int sigLen = 0;
    SmsSignature((const char*)priKeyPath.c_str(), (const char*)message.c_str(), message.length(), (unsigned char*)signatureBuf.c_str(), &sigLen);
    cout << sigLen << endl;
    // verify
    VerifySignature((const char*)pubKeyPath.c_str(), (const char*)message.c_str(), message.length(), (unsigned char*)signatureBuf.c_str(), sigLen);
    return 0;
}